/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.security;

import com.google.common.collect.Maps;
import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyResourceConfigurer;
import org.springframework.beans.factory.support.AbstractBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
import org.springframework.core.env.AbstractPropertyResolver;
import org.springframework.core.env.ConfigurablePropertyResolver;
import org.springframework.core.env.MutablePropertySources;
import org.springframework.core.env.PropertySourcesPropertyResolver;
import org.springframework.core.io.support.PropertiesLoaderSupport;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringValueResolver;
import org.vacoor.nothing.common.util.Pair;
import org.vacoor.nothing.common.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by Administrator on 2015/3/5.
 */
public class ReloadablePropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer implements BeanNameAware, BeanFactoryAware {
    private BeanFactory beanFactory;
    private String beanName;
    private Map<String/*placeholder*/, List<Pair<String/*property path*/, ConfigurableListableBeanFactory/**/>>> monited = Maps.newHashMap();

    @Override
    protected void processProperties(final ConfigurableListableBeanFactory beanFactoryToProcess, final ConfigurablePropertyResolver propertyResolver) throws BeansException {
        // super.processProperties(beanFactoryToProcess, propertyResolver);
        propertyResolver.setPlaceholderPrefix(this.placeholderPrefix);
        propertyResolver.setPlaceholderSuffix(this.placeholderSuffix);
        propertyResolver.setValueSeparator(this.valueSeparator);

        String[] beanNames = beanFactoryToProcess.getBeanDefinitionNames();
        for (String curName : beanNames) {
            // Check that we're not parsing our own bean definition,
            // to avoid failing on unresolvable placeholders in properties file locations.
            if (!(curName.equals(this.beanName) && beanFactoryToProcess.equals(this.beanFactory))) {
                final BeanDefinition bd = beanFactoryToProcess.getBeanDefinition(curName);

                try {
                    new BeanDefinitionVisitor2() {
                        @Override
                        protected String doResolveStringValue(final String propertyPath, final String strVal) {
                            return new PropertySourcesPropertyResolver(new MutablePropertySources()) {
                                /*
                                @Override
                                protected String getPropertyAsRawString(String key) {
                                    System.out.println(strVal + " = " + propertyPath);
                                    // return super.getPropertyAsRawString(key);
                                    return null;
                                }
                                */
                                public String getProperty(String key) {
//                                    if (key.startsWith("${") && key.endsWith("}")) {
                                        List<Pair<String, ConfigurableListableBeanFactory>> pairs = monited.get(key);
                                        if (null == pairs) {
                                            pairs = new ArrayList<Pair<String, ConfigurableListableBeanFactory>>();
                                            monited.put(key, pairs);
                                        }
                                        pairs.add(new Pair<String, ConfigurableListableBeanFactory>(propertyPath, beanFactoryToProcess));
                                        System.out.println(key + " = " + propertyPath);
//                                    }
                                    return null;
                                }
                            }.resolvePlaceholders(strVal);
                        }
                    }.visitBeanDefinition(curName + "#E#", bd);
                    // visitor.visitBeanDefinition(bd);
                } catch (Exception ex) {
                    throw new BeanDefinitionStoreException(bd.getResourceDescription(), curName, ex.getMessage(), ex);
                }
            }
        }
    }

    @Override
    public int getOrder() {
        return super.getOrder() - 10;
    }

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
        super.setBeanName(beanName);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
        super.setBeanFactory(beanFactory);
    }

    public void reload() {
        for (Map.Entry<String, List<Pair<String, ConfigurableListableBeanFactory>>> entry : monited.entrySet()) {
            //
            String key = entry.getKey();
            List<Pair<String, ConfigurableListableBeanFactory>> values = entry.getValue();
            for (Pair<String, ConfigurableListableBeanFactory> value : values) {
                String propertyPath = value.getFirst();
                ConfigurableListableBeanFactory beanFactory = value.getSecond();
                int i = propertyPath.indexOf("#E#");
                String name = propertyPath.substring(0, i);
                int j = propertyPath.lastIndexOf(".");

                if (name.equals(this.beanName) && beanFactory.equals(this.beanFactory)) {
                    continue;
                }

                BeanDefinition bd = beanFactory.getBeanDefinition(name);
                MutablePropertyValues mutablePropertyValues = resolvePropertyPath(bd.getPropertyValues(), propertyPath.substring(i + 3, j));
                mutablePropertyValues.add(propertyPath.substring(j + 1), "${" + key + "}");

                beanFactory.applyBeanPropertyValues(beanFactory.getBean(name), beanName);
                Map<String, PropertyResourceConfigurer> beansOfType = beanFactory.getBeansOfType(PropertyResourceConfigurer.class);
                for (PropertyResourceConfigurer configurer : beansOfType.values()) {
                    if (configurer.equals(this)) { continue; }
                    boolean override = true;
                    try {
                        Field f = ReflectionUtils.findField(PropertiesLoaderSupport.class, "localOverride");
                        ReflectionUtils.makeAccessible(f);
                        override = (Boolean) ReflectionUtils.getField(f, configurer);
                    } catch (Exception e) {}
                    configurer.setLocalOverride(true);
                    configurer.postProcessBeanFactory(beanFactory);
                    configurer.setLocalOverride(override);
                    // 清除缓存的信息
                    // acbf.clearMergedBeanDefinition(beanName);
                    // ((DefaultListableBeanFactory) acbf).removeBeanDefinition(beanName); // 该方法会彻底销毁, 不行
                    Method m = ReflectionUtils.findMethod(AbstractBeanFactory.class, "clearMergedBeanDefinition", String.class);
                    ReflectionUtils.makeAccessible(m);
                    ReflectionUtils.invokeMethod(m, beanFactory, beanName);
                    beanFactory.applyBeanPropertyValues(beanFactory.getBean(name), name);
                }
            }
            // bean beanFactory  property plho
        }
    }

    public static void refreshPropertyResourceConfigurer(ApplicationContext applicationContext) {
        Map<String, PropertyResourceConfigurer> configurersToProcess = applicationContext.getBeansOfType(PropertyResourceConfigurer.class);
        for (PropertyResourceConfigurer configurer : configurersToProcess.values()) {
            if (applicationContext instanceof ConfigurableListableBeanFactory) {
                configurer.postProcessBeanFactory((ConfigurableListableBeanFactory) applicationContext);
            }
            AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                configurer.postProcessBeanFactory((ConfigurableListableBeanFactory) beanFactory);
            }
        }
    }

    protected MutablePropertyValues resolvePropertyPath(MutablePropertyValues propertyValues, String propertyPath) {
        if (!StringUtils.hasText(propertyPath)) {
            return propertyValues;
        }
        int i = propertyPath.indexOf(".");
        if (i < 0) {
            propertyValues = (MutablePropertyValues) propertyValues.getPropertyValue(propertyPath).getValue();
        } else {
            propertyValues = (MutablePropertyValues) propertyValues.getPropertyValue(propertyPath.substring(0, i)).getValue();

        }
        return resolvePropertyPath(propertyValues, propertyPath.substring(i + 1));
    }

    private abstract class Resolver extends AbstractPropertyResolver implements StringValueResolver {

        @Override
        public boolean containsProperty(String key) {
            return false;
        }

        @Override
        public String getProperty(String key) {
            return null;
        }

        @Override
        public <T> T getProperty(String key, Class<T> targetType) {
            return null;
        }

        @Override
        public <T> Class<T> getPropertyAsClass(String key, Class<T> targetType) {
            return null;
        }

        @Override
        public String resolveStringValue(String strVal) {
            return resolvePlaceholders(strVal);
        }
    }
}
